package linshuai;

import java.util.Random;

public class CAModel
{
	/*
	 * Step rules
	 * 0. create v0*area virus (same string), x0*area Th, y0*area Tcs
	 * 1. forall
	 * 1.1 move at most rm
	 * 1.1.1 if same type there: swap
	 * 2. forall hiv+cd4
	 *   2.1 infect at P=pv, if cd4 not infection already
	 *     2.1.1 virus mutation at P=mv, one bit flips
	 *     2.1.2 virus gets integrated into the cell
	 *   2.2 proliferate at Phamming(p0)
	 *     2.2.1 create nx within range rx, if none there
	 * 3. forall infected cd4
	 *   3.1 internally: virus++  (P=bv?)
	 *   3.2 when internal virus == nlys
	 *     3.2.1 cell dies
	 *     3.2.2 create nlys virus within range rv, if none there
	 * 4. cd8+cd4 (infected and virus>0)
	 *   4.1 cd4 killed P=Phamming(k0)
	 *   4.2 cd8 proliferates at P=Phamming(p0_) with ny at range ry
	 * 5. foreach virus
	 *   5.1 virus of strain i die at P=dvi=dv0+(1-dv0)*cd4Hill(i)
	 * 6. for each cd8
	 *   6.1 cd8 of strain i die at P=dyi=dy0*(1-cd4Hill(i))
	 * 7. create cd4 n=x0*dx0 at random pos
	 * 8. create cd8 n=y0*dy0 at random pos
	 *
	 * Explanation
	 * The reason why this works out is because at the beginning there is a wide
	 * range of T-cell specificities, but only one hiv specificity. HIV kills
	 * all Th indiscriminately, but it also (indirectly) "binds" and stimulates
	 * Th (and, by proxy, Tc) to proliferate. Tc start killing hiv. The system
	 * has much difficulty completely eliminating every trace of HIV at any
	 * moment, which means that, as mutations accumulate, the system starts
	 * working less well and less well. (There is an assymetry in that HIV "binds"
	 * to any Th, but Th and Tc only "bind" to specific HIV.)
	 * There seems to be more stochasticity in our graphic than in the article's
	 * Figure 1. But the article plots only every 365 ticks (aka days). It does
	 * look more like Figure 6.
	 */

	public static final int L = 100;

	private static final float pv = 0.8f;  // probability virion infects healthy CD4
	private static final float bv = 0.5f;  // probability infected CD4 generate virion
	private static final float mv = 5.5e-5f;  // probability mutation occurs during transcription
	private static final int nlys = 5;  // lysis threshold infected CD4
	private static final float p0 = 0.8f;  // baseline probability T cell activated by virion
	private static final float p0_ = 0.8f;
	private static final int nx = 5;  // number of offspring producing during T proliferation
	private static final int ny  = 5;
	private static final float k0 = 0.8f;  // probability virion killed by CD8
	private static final float dv0 = 0.01f;  // natural death probability virion
	private static final float dx0 = 0.01f;  // death probability healthy CD4
	private static final float dy0 = 0.1f;
	private static final float alpha = 1.0f;  // regulation parameter
	private static final float theta = 0.5f;
	public static final int l = 10;  // string size

	private static final int rm = 3;  // migration range
	private static final int rv = 3;
	private static final int rx = 3;
	private static final int ry = 3;

	private static final float v0 = 0.03f;  // initial viral density
	private static final float x0 = 0.8f;  // initial CD4 density
	private static final float y0 = 0.1f;  // initial CD8 density

	private static final int HIV=0, CD4=1, CD8=2;

	private class Agent {
		private int str;
		private int virus_str, virus_nbr;  // CD4
		public Agent() {
			str = random.nextInt((int)Math.pow(2,CAModel.l));
			virus_str = -1;
		}
		public Agent(int parent_str) {
			str = parent_str;
			virus_str = -1;
		}
		public boolean infected() {
			return virus_str >= 0;
		}
	}

	private static class Site {
		Agent agent[] = new Agent[3];
	}

	private static class CA {
		private Site ca[][];
		private int count[] = new int[3];
		private int strains[][] = new int[3][(int)Math.pow(2,CAModel.l)];

		public CA() {
			ca = new Site[L][L];
			for(int y = 0; y < L; y++)
				for(int x = 0; x < L; x++)
					ca[x][y] = new Site();
		}

		public boolean has(int x, int y, int t) {
			return get(x,y,t) != null;
		}
		public Agent get(int x, int y, int t) {
			return ca[x][y].agent[t];
		}
		public void set(int x, int y, int t, Agent a) {
			Agent b = ca[x][y].agent[t];
			if(b != null) {
				count[t]--;
				strains[t][b.str]--;
			}

			ca[x][y].agent[t] = a;
			if(a != null) {
				count[t]++;
				strains[t][a.str]++;
			}
		}
	}

	private static int hamming(int x, int y) {
		int dist = 0, val = x^y;  // XOR
		while(val != 0) {  // count nbr of bits
			dist++;
			val &= val-1;
		}
		return dist;
	}

	private static double Phamming(float p, Agent a, Agent b) {
		return p * Math.exp(-alpha*hamming(a.str, b.str));
	}

	private float cd4Hill(int i) {
		float cd4i = ((float)ca.strains[CD4][i]) / (L*L);
		//sfloat cd4i = ((float)ca.cd4Strains[i]) / ca.count[CD4];
		return cd4i/(theta+cd4i);
	}

	private boolean P(double p) {
		return random.nextDouble() < p;
	}

	private CA ca = new CA();
	private Random random = new Random();
	private int ticks;

	public CAModel() {
		float i0[] = new float[]{v0,x0,y0};
		int hiv_str = random.nextInt((int)Math.pow(2,CAModel.l));
		for(int t = 0; t < 3; t++)
			for(int i = 0; i < i0[t]*L*L;) {
				int x = random.nextInt(L);
				int y = random.nextInt(L);
				if(!ca.has(x,y,t)) {
					Agent a = (t==HIV) ? new Agent(hiv_str) : new Agent();
					ca.set(x,y,t,a);
					i++;
				}
			}
	}

	private void proliferate(int t, int x, int y, int str, int n, int r) {
		for(int i = 0; i < n; i++) {
			int x2 = x + random.nextInt((r+1)*2)-r;
			int y2 = y + random.nextInt((r+1)*2)-r;
			if(x2 < 0)  x2 += L;
			if(x2 >= L) x2 -= L;
			if(y2 < 0)  y2 += L;
			if(y2 >= L) y2 -= L;
			if(!ca.has(x2,y2,t))
				ca.set(x2,y2,t,new Agent(str));
		}
	}

	public void step() {
		/*int oldcount[] = new int[3];
		System.arraycopy(ca.count, 0, oldcount, 0, 3);*/
		// 1. forall
		for(int x = 0; x < L; x++)
			for(int y = 0; y < L; y++)
				for(int t = 0; t < 3; t++)
					if(ca.has(x,y,t)) {
						// 1.1 move at most rm
						int x2 = x + random.nextInt((rm+1)*2)-rm;
						int y2 = y + random.nextInt((rm+1)*2)-rm;
						if(x2 < 0)  x2 += L;
						if(x2 >= L) x2 -= L;
						if(y2 < 0)  y2 += L;
						if(y2 >= L) y2 -= L;
						Agent a = ca.get(x,y,t);
						ca.set(x,y,t,null);
						if(ca.has(x2,y2,t))  // 1.1.1 if same type there: swap
							ca.set(x,y,t, ca.get(x2,y2,t));
						ca.set(x2,y2,t,a);
					}
		// 2. forall hiv+cd4
		for(int x = 0; x < L; x++)
			for(int y = 0; y < L; y++)
				if(ca.has(x, y, HIV) && ca.has(x,y,CD4)) {
					if(!ca.get(x,y,CD4).infected()) {
						// 2.2 proliferate at Phamming(p0)
						if(P(Phamming(p0, ca.get(x,y,CD4), ca.get(x,y,HIV)))) {
							// 2.2.1 create nx within range rx, if none there
							//System.out.format("cd4 proliferate - Phamming: %f\n", Phamming(p0, ca.get(x,y,CD4), ca.get(x,y,HIV)));
							proliferate(CD4, x, y, ca.get(x,y,CD4).str, nx, rx);
						}
						// 2.1 infect at P=pv, if cd4 not infection already
						if(P(pv)) {
							//System.out.println("cd4 infected");
							// 2.1.1 virus mutation at P=mv, one bit flips
							int s = ca.get(x,y,HIV).str;
							if(P(mv))
								s = s ^ (1 << random.nextInt(l));
							ca.get(x,y,CD4).virus_str = s;

							// 2.1.2 virus gets integrated into the cell
							ca.set(x,y,HIV,null);
						}
					}
				}
		// 3. forall infected cd4
		for(int x = 0; x < L; x++)
			for(int y = 0; y < L; y++) {
				Agent a = ca.get(x,y,CD4);
				if(a != null && a.infected()) {
					// 3.1 internally: virus++
					if(P(bv))
						a.virus_nbr++;
					// 3.2 when internal virus == nlys
					if(a.virus_nbr == nlys) {
						//System.out.println("cd4 killed - virus proliferates");

						// 3.2.1 cell dies
						ca.set(x,y,CD4,null);

						// 3.2.2 create nlys virus within range rv, if none there
						proliferate(HIV, x, y, a.virus_str, nlys, rv);
					}
				}
			}
		// 4. cd8+cd4 (infected and virus>0)
		for(int y = 0; y < L; y++)
			for(int x = 0; x < L; x++)
				if(ca.has(x,y,CD4) && ca.has(x,y,CD8))
					//if(ca.get(x,y,CD4).infected()) {
					if(ca.get(x,y,CD4).virus_nbr > 0) {
						// 4.2 cd8 proliferates at P=Phamming(p0_) with ny at range ry
						if(P(Phamming(p0_, ca.get(x,y,CD4), ca.get(x,y,CD8)))) {
							//System.out.println("cd8 proliferates");
							proliferate(CD8, x, y, ca.get(x,y,CD8).str, ny, ry);
						}

						// 4.1 cd4 killed P=Phamming(k0)
						if(P(Phamming(k0, ca.get(x,y,CD4), ca.get(x,y,CD8)))) {
							//System.out.println("cd8 kills cd4");
							ca.set(x,y,CD4,null);
						}
					}
		// 5. foreach virus
		// 6. foreach cd8
		for(int x = 0; x < L; x++)
			for(int y = 0; y < L; y++) {
				if(ca.has(x,y,HIV)) {
					Agent a = ca.get(x,y,HIV);
					// 5.1 virus of strain i die at P=dvi=dv0+(1-dv0)*cd4Hill(i)
					if(P(dv0+(1-dv0)*cd4Hill(a.str))) {
						//System.out.println("virus killed");
						ca.set(x,y,HIV,null);
					}
				}
				if(ca.has(x,y,CD8)) {
					Agent a = ca.get(x,y,CD8);
					// 6.1 cd8 of strain i die at P=dyi=dy0*(1-cd4Hill(i))
					if(P(dy0*(1-cd4Hill(a.str)))) {
						//System.out.println("cd8 dies");
						ca.set(x,y,CD8,null);
					}
					//else System.out.format("resisted %d - P: %f\n", a.str, 1-cd4Hill(a.str));
				}
		}
		// 7. create cd4 n=x0*dx0 at random pos
		for(int i = 0; i < (L*L)*x0*dx0; i++) {
			int x = random.nextInt(L);
			int y = random.nextInt(L);
			if(!ca.has(x,y,CD4))
				//System.out.println("cd4 created");
				ca.set(x,y,CD4,new Agent());
		}
		// 8. create cd8 n=y0*dy0 at random pos
		for(int i = 0; i < (L*L)*y0*dy0; i++) {
			int x = random.nextInt(L);
			int y = random.nextInt(L);
			if(!ca.has(x,y,CD8))
				//System.out.println("cd8 created");
				ca.set(x,y,CD8,new Agent());
		}
		/*System.out.format("%d - hiv: %d , cd4: %d , cd8: %d\n", ticks,
			ca.count[0]-oldcount[0], ca.count[1]-oldcount[1], ca.count[2]-oldcount[2]);*/
		ticks++;
	}

	public boolean has(int x, int y, int t) {
		return ca.has(x,y,t);
	}

	public int getTicks() {
		return ticks;
	}

	public Float[] getDensities() {
		Float c[] = new Float[3];
		for(int t = 0; t < 3; t++)
			c[t] = ((float)ca.count[t]) / (L*L);
		return c;
	}

	public Integer[] getStrainsCounts() {
		Integer c[] = new Integer[3];
		for(int t = 0; t < 3; t++) {
			c[t] = 0;
			for(int i = 0; i < ca.strains[t].length; i++)
				if(ca.strains[t][i] > 0 && ca.strains[HIV][i] > 0)
					c[t]++;
		}
		return c;
	}
}
